package com.mazaiting.manager.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mazaiting.auth.constant.AuthConstant;
import com.mazaiting.common.core.domain.result.Option;
import com.mazaiting.common.core.domain.result.ResultCode;
import com.mazaiting.common.core.ex.exceptions.BaseException;
import com.mazaiting.common.core.ex.exceptions.BusinessException;
import com.mazaiting.common.core.ex.exceptions.EmptyObjectException;
import com.mazaiting.web.holder.SpringContextHolder;
import com.mazaiting.manager.constant.SystemConstant;
import com.mazaiting.manager.constant.enums.MenuTypeEnum;
import com.mazaiting.manager.convert.IMenuConverter;
import com.mazaiting.manager.domain.entity.SysMenu;
import com.mazaiting.manager.domain.entity.SysPermission;
import com.mazaiting.manager.domain.form.MenuForm;
import com.mazaiting.manager.domain.query.MenuQueryParam;
import com.mazaiting.manager.domain.vo.menu.MenuDetailVO;
import com.mazaiting.manager.domain.vo.menu.MenuVO;
import com.mazaiting.manager.domain.vo.menu.ResourceVO;
import com.mazaiting.manager.domain.vo.menu.RouteVO;
import com.mazaiting.manager.listeners.event.RuleRelationEvent;
import com.mazaiting.manager.mapper.ISysMenuMapper;
import com.mazaiting.manager.mapper.ISysPermissionMapper;
import com.mazaiting.manager.service.ISysMenuService;
import com.mazaiting.manager.service.ISysRoleMenuService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * sys_menu菜单表 服务实现类
 *
 * @author 党海亮
 * @since 2022-03-22
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends ServiceImpl<ISysMenuMapper, SysMenu> implements ISysMenuService {
    private final ISysRoleMenuService sysRoleMenuService;

    private final ISysPermissionMapper sysPermissionMapper;

    private final IMenuConverter menuConverter;


    @Override
    public List<RouteVO> listRoutes() throws EmptyObjectException {
        // 查询所有菜单及管理角色
        List<SysMenu> menuList = baseMapper.listRoutes();
        if (CollectionUtil.isEmpty(menuList)) {
            throw new EmptyObjectException(ResultCode.MENU_LIST_IS_NULL);
        }
        // 递归生成路由树
        return recurRoutes(SystemConstant.ROOT_MENU_ID, menuList);
    }

    @Override
    public List<ResourceVO> listResources() {
        // 查询所有菜单
        List<SysMenu> menuList = lambdaQuery().orderByAsc(SysMenu::getSort).list();
        // 查询所有权限
        List<SysPermission> permList = sysPermissionMapper.selectList(Wrappers.emptyWrapper());
        // 菜单与权限关联
        return recurResources(SystemConstant.ROOT_MENU_ID, menuList, permList);
    }

    @Override
    public List<MenuVO> listMenus(MenuQueryParam queryParam) {
        // 菜单名
        String name = queryParam.getName();
        // 条件过滤
        List<SysMenu> menus = lambdaQuery().like(StringUtils.hasLength(name), SysMenu::getName, name)
                .orderByAsc(SysMenu::getSort).list();
        // 菜单 ID
        Set<Long> cacheMenuIds = menus.stream().map(SysMenu::getId).collect(Collectors.toSet());
        // 表格菜单数据
        return menus.stream().map(menu -> {
            Long parentId = menu.getParentId();
            // parentId不在当前菜单ID的列表，说明为顶级菜单ID，根据此ID作为递归的开始条件节点
            if (!cacheMenuIds.contains(parentId)) {
                cacheMenuIds.add(parentId);
                return recurTableMenus(parentId, menus);
            }
            return new LinkedList<MenuVO>();
        }).collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll);
    }

    @Override
    public List<Option<Long>> listMenuOptions() {
        List<SysMenu> menuList = lambdaQuery().orderByAsc(SysMenu::getSort).list();
        return recurMenus(SystemConstant.ROOT_MENU_ID, menuList);
    }

    @Override
    public MenuDetailVO findById(Long menuId) throws EmptyObjectException {
        SysMenu sysMenu = getById(menuId);
        if (Objects.isNull(sysMenu)) {
            throw new EmptyObjectException(ResultCode.MENU_NOT_EXIST);
        }
        return menuConverter.entity2DetailVO(sysMenu);
    }

    @Override
    public boolean save(MenuForm menuForm) throws BaseException {
        // 菜单路径
        String path = menuForm.getPath();
        // 根据菜单路径与父 ID 判断路径是否已存在
        Optional<SysMenu> sysMenuOpt = lambdaQuery().eq(SysMenu::getPath, path).eq(SysMenu::getParentId, menuForm.getParentId()).oneOpt();
        if (sysMenuOpt.isPresent()) {
            throw new EmptyObjectException(ResultCode.MENU_EXIST);
        }
        // form -> entity
        SysMenu sysMenu = menuConverter.form2Entity(menuForm);
        // 清空 ID
        sysMenu.setId(null);
        // 设置默认组件
        setDefaultComponent(sysMenu, path);
        // 保存菜单
        boolean result = save(sysMenu) && sysRoleMenuService.save(AuthConstant.AUTH_ROOT_ID, sysMenu.getId());
        if (result) {
            SpringContextHolder.publishEvent(new RuleRelationEvent());
        }
        return result;
    }

    @Override
    public boolean update(MenuForm menuForm) throws BaseException {
        // 菜单 ID
        Long menuId = menuForm.getId();
        // 根据 ID 查询菜单是否存在
        SysMenu sysMenu = getById(menuId);
        if (Objects.isNull(sysMenu)) {
            throw new EmptyObjectException(ResultCode.MENU_NOT_EXIST);
        }

        // 菜单路径
        String path = menuForm.getPath();
        // 根据ID不相等并且菜单路径与父 ID 判断路径是否已存在
        Optional<SysMenu> sysMenuOpt = lambdaQuery()
                .ne(SysMenu::getId, menuId)
                .eq(SysMenu::getPath, path)
                .eq(SysMenu::getParentId, menuForm.getParentId())
                .oneOpt();
        if (sysMenuOpt.isPresent()) {
            throw new EmptyObjectException(ResultCode.MENU_EXIST);
        }
        // form => entity
        menuConverter.form2Entity(menuForm, sysMenu);
        // 设置默认组件
        setDefaultComponent(sysMenu, path);
        // 保存菜单
        boolean result = updateById(sysMenu);
        if (result) {
            SpringContextHolder.publishEvent(new RuleRelationEvent());
        }
        return result;
    }

    @Override
    public boolean deleteByIds(String ids) {
        // 菜单 ID
        List<Long> idList = Arrays.stream(ids.split(",")).mapToLong(Long::parseLong).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        // 根据 ID 批量删除
        boolean result = removeBatchByIds(idList);
        if (result) {
            SpringContextHolder.publishEvent(new RuleRelationEvent());
        }
        return result;
    }

    /**
     * 递归生成菜单路由层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 路由列表
     */
    private List<RouteVO> recurRoutes(Long parentId, List<SysMenu> menuList) {
        List<RouteVO> list = new ArrayList<>();
        Optional.ofNullable(menuList).ifPresent(menus -> menus.stream().filter(menu -> menu.getParentId().equals(parentId))
                .forEach(menu -> {
                    RouteVO routeVO = new RouteVO();
                    // 菜单类型
                    MenuTypeEnum menuTypeEnum = menu.getType();
                    // 如果是菜单, 则名字设置为路径
                    if (MenuTypeEnum.MENU.equals(menuTypeEnum)) {
                        routeVO.setName(menu.getPath()); //  根据name路由跳转 this.$router.push({name:xxx})
                    }
                    // 设置路径
                    routeVO.setPath(menu.getPath()); // 根据path路由跳转 this.$router.push({path:xxx})
                    routeVO.setRedirect(menu.getRedirect());
                    routeVO.setComponent(menu.getComponent());

                    // 设置源数据
                    RouteVO.Meta meta = new RouteVO.Meta();
                    meta.setTitle(menu.getName());
                    meta.setIcon(menu.getIcon());
                    meta.setRoles(menu.getRoles());
                    meta.setHidden(!menu.getVisible());
                    meta.setKeepAlive(true);
                    // 设置源数据
                    routeVO.setMeta(meta);
                    List<RouteVO> children = recurRoutes(menu.getId(), menuList);
                    // 含有子节点的目录设置为可见
                    boolean alwaysShow = CollectionUtil.isNotEmpty(children) && children.stream().anyMatch(item -> Objects.equals(false, item.getMeta().getHidden()));
                    meta.setAlwaysShow(alwaysShow);
                    routeVO.setChildren(children);

                    list.add(routeVO);
                }));
        return list;
    }


    /**
     * 递归生成资源(菜单+权限)树形列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 资源（菜单+权限）树形列表
     */
    private static List<ResourceVO> recurResources(Long parentId, List<SysMenu> menuList, List<SysPermission> permList) {
        return Optional.ofNullable(menuList).orElse(new ArrayList<>()).stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(menu -> {
                    Long menuId = menu.getId();
                    // 菜名名称
                    ResourceVO resourceVO = new ResourceVO();
                    resourceVO.setValue(menu.getId());
                    resourceVO.setLabel(menu.getName());
                    // 递归生成子菜单
                    List<ResourceVO> children = recurResources(menu.getId(), menuList, permList);
                    resourceVO.setChildren(children);
                    // 设置当前菜单权限
                    List<Option<Long>> perms = permList.stream().filter(perm -> perm.getMenuId().equals(menuId))
                            .map(item -> new Option<>(item.getId(), item.getName()))
                            .collect(Collectors.toList());
                    resourceVO.setPerms(perms);
                    return resourceVO;
                }).collect(Collectors.toList());
    }

    /**
     * 递归生成菜单表格层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 菜单表格层级列表
     */
    private List<MenuVO> recurTableMenus(Long parentId, List<SysMenu> menuList) {
        return Optional.ofNullable(menuList).orElse(new ArrayList<>())
                .stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(entity -> {
                    MenuVO menuVO = menuConverter.entity2VO(entity);
                    List<MenuVO> children = recurTableMenus(entity.getId(), menuList);
                    menuVO.setChildren(children);
                    return menuVO;
                }).collect(Collectors.toList());
    }

    /**
     * 递归生成菜单下拉层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 菜单下拉层级列表
     */
    private static List<Option<Long>> recurMenus(Long parentId, List<SysMenu> menuList) {
        return Optional.ofNullable(menuList).orElse(new ArrayList<>()).stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(menu -> new Option<>(menu.getId(), menu.getName(), recurMenus(menu.getId(), menuList)))
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }

    /**
     * 设置默认组件
     * @param sysMenu 系统菜单
     * @param path 系统路径
     */
    private void setDefaultComponent(SysMenu sysMenu, String path) throws BusinessException {
        switch (sysMenu.getType()) {
            // 目录
            case CATALOG -> {
                // 菜单父 ID 是顶级目录
                if (SystemConstant.ROOT_MENU_ID.equals(sysMenu.getParentId()) ) {
                    // 并且 不是以 / 开头
                    if (!path.startsWith(SystemConstant.MENU_PATH_PREFIX)) {
                        throw new BusinessException(ResultCode.MENU_PATH_FORMAT_ERROR);
                    }
                    // 根目录设置默认路由
                    sysMenu.setComponent(SystemConstant.DEFAULT_LAYOUT);
                }
            }
            // 外链
            case EXT_LINK -> sysMenu.setComponent(null);
        }
    }
}
